package com.huawei.insa2.util;

import java.io.*;
import java.net.URL;
import java.util.*;

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.huawei.smproxy.util.Args;

public class Cfg
{
    public Cfg(String url) throws IOException
    {
        this(url, false);
    }

    @SuppressWarnings("deprecation")
    public Cfg(String url, boolean create) throws IOException
    {
        if (url == null)
        {
            throw new IllegalArgumentException("url is null");
        }
        
        if (url.indexOf(':') > 1)
        {
            file = url;
        }
        else
        {
            file = (new File(url)).toURL().toString();
        }
        
        new URL(file);
        
        try
        {
            load();
        }
        catch (FileNotFoundException ex)
        {
            if (!create)
            {
                throw ex;
            }
            else
            {
                loadXMLParser();
                doc = builder.newDocument();
                root = doc.createElement("config");
                doc.appendChild(root);
                isDirty = true;
                flush();
                return;
            }
        }
    }

    public Args getArgs(String key)
    {
        Map<String, String> args = new HashMap<String, String>();
        String children[] = childrenNames(key);
        for (int i = 0; i < children.length; i++)
            args.put(
                    children[i],
                    get(String.valueOf(String.valueOf((new StringBuffer(String
                            .valueOf(String.valueOf(key)))).append('/').append(
                            children[i]))), null));

        return new Args(args);
    }

    private static void writeIndent(PrintWriter pw, int level)
    {
        for (int i = 0; i < level; i++)
            pw.print(indent);
    }

    private static void writeNode(Node node, PrintWriter pw, int deep)
    {
        switch (node.getNodeType())
        {
            case 8: // '\b'
            {
                writeIndent(pw, deep);
                pw.print("<!--");
                pw.print(node.getNodeValue());
                pw.println("-->");
                return;
            }
            case 3: // '\003'
            {
                String value = node.getNodeValue().trim();
                if (value.length() == 0)
                    return;
                writeIndent(pw, deep);
                for (int i = 0; i < value.length(); i++)
                {
                    char c = value.charAt(i);
                    switch (c)
                    {
                        case 60: // '<'
                        {
                            pw.print("&lt;");
                            break;
                        }
                        case 62: // '>'
                        {
                            pw.print("&gt;");
                            break;
                        }
                        case 38: // '&'
                        {
                            pw.print("&amp;");
                            break;
                        }
                        case 39: // '\''
                        {
                            pw.print("&apos;");
                            break;
                        }
                        case 34: // '"'
                        {
                            pw.print("&quot;");
                            break;
                        }
                        default:
                        {
                            pw.print(c);
                            break;
                        }
                    }
                }

                pw.println();
                return;
            }
            case 1: // '\001'
            {
                if (!node.hasChildNodes())
                {
                    return;
                }
                for (int i = 0; i < deep; i++)
                {
                    pw.print(indent);
                }

                String nodeName = node.getNodeName();
                pw.print('<');
                pw.print(nodeName);
                NamedNodeMap nnm = node.getAttributes();
                if (nnm != null)
                {
                    for (int i = 0; i < nnm.getLength(); i++)
                    {
                        Node attr = nnm.item(i);
                        pw.print(' ');
                        pw.print(attr.getNodeName());
                        pw.print("=\"");
                        pw.print(attr.getNodeValue());
                        pw.print('"');
                    }

                }
                if (node.hasChildNodes())
                {
                    NodeList children = node.getChildNodes();
                    if (children.getLength() == 0)
                    {
                        pw.print('<');
                        pw.print(nodeName);
                        pw.println("/>");
                        return;
                    }
                    if (children.getLength() == 1)
                    {
                        Node n = children.item(0);
                        if (n.getNodeType() == 3)
                        {
                            String v = n.getNodeValue();
                            if (v != null)
                                v = v.trim();
                            if (v == null || v.length() == 0)
                            {
                                pw.println(" />");
                                return;
                            }
                            else
                            {
                                pw.print('>');
                                pw.print(v);
                                pw.print("</");
                                pw.print(nodeName);
                                pw.println('>');
                                return;
                            }
                        }
                    }
                    pw.println(">");
                    for (int i = 0; i < children.getLength(); i++)
                        writeNode(children.item(i), pw, deep + 1);

                    for (int i = 0; i < deep; i++)
                        pw.print(indent);

                    pw.print("</");
                    pw.print(nodeName);
                    pw.println(">");
                }
                else
                {
                    pw.println("/>");
                }
                return;
            }
            case 9: // '\t'
            {
                pw.println(XML_HEAD);
                NodeList nl = node.getChildNodes();
                for (int i = 0; i < nl.getLength(); i++)
                {
                    writeNode(nl.item(i), pw, 0);
                }
                return;
            }
            case 2: // '\002'
            case 4: // '\004'
            case 5: // '\005'
            case 6: // '\006'
            case 7: // '\007'
            default:
                return;
        }
    }

    private Node findNode(String key)
    {
        Node ancestor = root;
        StringTokenizer st = new StringTokenizer(key, "/");
        label0: do
        {
            if (st.hasMoreTokens())
            {
                String nodeName = st.nextToken();
                NodeList nl = ancestor.getChildNodes();
                int i = 0;
                do
                {
                    if (i >= nl.getLength())
                    {
                        continue label0;
                    }
                    Node n = nl.item(i);
                    if (nodeName.equals(n.getNodeName()))
                    {
                        ancestor = n;
                        if (!st.hasMoreTokens())
                        {
                            return n;
                        }
                        continue label0;
                    }
                    i++;
                }
                while (true);
            }
            return null;
        }
        while (true);
    }

    private Node createNode(String key)
    {
        Node ancestor = root;
        StringTokenizer st = new StringTokenizer(key, "/");
        label0: do
            if (st.hasMoreTokens())
            {
                String nodeName = st.nextToken();
                NodeList nl = ancestor.getChildNodes();
                int i = 0;
                do
                {
                    if (i >= nl.getLength())
                        break;
                    Node n = nl.item(i);
                    if (nodeName.equals(n.getNodeName()))
                    {
                        ancestor = n;
                        if (!st.hasMoreTokens())
                            return ancestor;
                        continue label0;
                    }
                    i++;
                }
                while (true);
                do
                {
                    Node n = doc.createElement(nodeName);
                    ancestor.appendChild(n);
                    ancestor = n;
                    if (!st.hasMoreTokens())
                        return ancestor;
                    nodeName = st.nextToken();
                }
                while (true);
            }
            else
            {
                return null;
            }
        while (true);
    }

    public String get(String key, String def)
    {
        if (key == null)
            throw new NullPointerException("parameter key is null");
        Node node = findNode(key);
        if (node == null)
            return def;
        NodeList nl = node.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++)
            if (nl.item(i).getNodeType() == 3)
                return nl.item(i).getNodeValue().trim();

        node.appendChild(doc.createTextNode(def));
        return def;
    }

    public void put(String key, String value)
    {
        if (key == null)
            throw new NullPointerException("parameter key is null");
        if (value == null)
            throw new NullPointerException("parameter value is null");
        value = value.trim();
        Node node = createNode(key);
        if (null == node)
        {
            return;
        }
        NodeList nl = node.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++)
        {
            Node child = nl.item(i);
            if (child.getNodeType() != 3)
                continue;
            String childValue = child.getNodeValue().trim();
            if (childValue.length() == 0)
                continue;
            if (childValue.equals(value))
            {
                return;
            }
            else
            {
                child.setNodeValue(value);
                isDirty = true;
                return;
            }
        }

        if (nl.getLength() == 0)
        {
            node.appendChild(doc.createTextNode(value));
        }
        else
        {
            Node f = node.getFirstChild();
            if (f.getNodeType() == 3)
                f.setNodeValue(value);
            else
                node.insertBefore(doc.createTextNode(value), f);
        }
        isDirty = true;
    }

    public boolean getBoolean(String key, boolean def)
    {
        String str = String.valueOf(def);
        String resstr = get(key, str);
        Boolean resboolean = Boolean.valueOf(resstr);
        boolean result = resboolean.booleanValue();
        return result;
    }

    public int getInt(String key, int def)
    {
        String str = String.valueOf(def);
        String resstr = get(key, str);
        int result;
        try
        {
            result = Integer.parseInt(resstr);
        }
        catch (NumberFormatException e)
        {
            int i = def;
            return i;
        }
        return result;
    }

    public float getFloat(String key, float def)
    {
        String str = String.valueOf(def);
        String resstr = get(key, str);
        float result;
        try
        {
            result = Float.parseFloat(resstr);
        }
        catch (NumberFormatException e)
        {
            float f = def;
            return f;
        }
        return result;
    }

    public double getDouble(String key, double def)
    {
        String str = String.valueOf(def);
        String resstr = get(key, str);
        double result;
        try
        {
            result = Double.parseDouble(resstr);
        }
        catch (NumberFormatException e)
        {
            double d = def;
            return d;
        }
        return result;
    }

    public long getLong(String key, long def)
    {
        String str = String.valueOf(def);
        String resstr = get(key, str);
        long result;
        try
        {
            result = Long.parseLong(resstr);
        }
        catch (NumberFormatException e)
        {
            long l = def;
            return l;
        }
        return result;
    }

    public byte[] getByteArray(String key, byte def[])
    {
        try
        {
            String str = new String(def, "UTF-8");
            String resstr = get(key, str);
            byte result[];
            result = resstr.getBytes("UTF-8");
            return result;
        }
        catch (UnsupportedEncodingException e)
        {
            return new byte[]{};
        }
    }

    public void putBoolean(String key, boolean value)
    {
        String str = String.valueOf(value);
        try
        {
            put(key, str);
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    public void putInt(String key, int value)
    {
        String str = String.valueOf(value);
        try
        {
            put(key, str);
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    public void putFloat(String key, float value)
    {
        String str = String.valueOf(value);
        try
        {
            put(key, str);
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    public void putDouble(String key, double value)
    {
        String str = String.valueOf(value);
        try
        {
            put(key, str);
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    public void putLong(String key, long value)
    {
        String str = String.valueOf(value);
        try
        {
            put(key, str);
        }
        catch (RuntimeException e)
        {
            throw e;
        }
    }

    public void putByteArray(String key, byte value[])
    {
        put(key, Base64.encode(value));
    }

    public void removeNode(String key)
    {
        Node node = findNode(key);
        if (node == null)
            return;
        Node parentnode = node.getParentNode();
        if (parentnode != null)
        {
            parentnode.removeChild(node);
            isDirty = true;
        }
    }

    public void clear(String key)
    {
        Node node = findNode(key);
        if (node == null)
            throw new RuntimeException("InvalidName");
        Node lastnode = null;
        for (; node.hasChildNodes(); node.removeChild(lastnode))
            lastnode = node.getLastChild();

        if (lastnode != null)
            isDirty = true;
    }

    public String[] childrenNames(String key)
    {
        Node node = findNode(key);
        if (node == null)
            return new String[0];
        NodeList nl = node.getChildNodes();
        LinkedList<String> list = new LinkedList<String>();
        for (int i = 0; i < nl.getLength(); i++)
        {
            Node child = nl.item(i);
            if (child.getNodeType() == 1 && child.hasChildNodes())
                list.add(child.getNodeName());
        }

        String ret[] = new String[list.size()];
        for (int i = 0; i < ret.length; i++)
            ret[i] = (String) list.get(i);

        return ret;
    }

    public boolean nodeExist(String key)
    {
        Node theNode = findNode(key);
        if (theNode == null)
            return false;
        return theNode.hasChildNodes();
    }

    private void loadXMLParser() throws IOException
    {
        synchronized(this)
        {
            if (builder == null)
            {
                try
                {
                    factory = DocumentBuilderFactory.newInstance();
                    factory.setIgnoringComments(true);
                    builder = factory.newDocumentBuilder();
                }
                catch (ParserConfigurationException ex)
                {
                    throw new IOException("XML Parser load error:".concat(String
                            .valueOf(String.valueOf(ex.getLocalizedMessage()))));
                }
            }
        }
    }

    public void load() throws IOException
    {
        loadXMLParser();
        try
        {
            InputSource is = new InputSource(new InputStreamReader((new URL(
                    file)).openStream(), "UTF-8"));
            is.setEncoding(System.getProperty("file.encoding"));
            synchronized(this)
            {
                doc = builder.parse(is);
            }
        }
        catch (SAXException ex)
        {
            ex.printStackTrace();
            String message = ex.getMessage();
            Exception e = ex.getException();
            if (e != null)
                message = String.valueOf(message)
                        + String.valueOf("embedded exception:".concat(String
                                .valueOf(String.valueOf(e))));
            throw new IOException("XML file parse error:".concat(String
                    .valueOf(String.valueOf(message))));
        }
        root = doc.getDocumentElement();
        if (!"config".equals(root.getNodeName()))
            throw new IOException(
                    "Config file format error, root node must be <config>");
        else
            return;
    }

    public void flush() throws IOException
    {
        if (isDirty)
        {
            String proc = (new URL(file)).getProtocol().toLowerCase(Locale.getDefault());
            if (!proc.equalsIgnoreCase("file"))
                throw new UnsupportedOperationException(
                        "Unsupport write config URL on protocal ".concat(String
                                .valueOf(String.valueOf(proc))));
            String fileName = (new URL(file)).getPath();
//            Debug.dump((new URL(file)).getPath());
//            Debug.dump((new URL(file)).getFile());
//            BufferedOutputStream bos = new BufferedOutputStream(
//                    new FileOutputStream(fileName), 2048);
            PrintWriter pw = new PrintWriter(fileName, "UTF-8");
            writeNode(doc, pw, 0);
            pw.flush();
            pw.close();
            isDirty = false;
        }
    }

    private static DocumentBuilderFactory factory;

    private static DocumentBuilder builder;

    private static final String XML_HEAD = String
            .valueOf(String.valueOf((new StringBuffer(
                    "<?xml version=\"1.0\" encoding=\"")).append(
                    System.getProperty("file.encoding")).append("\"?>")));

    private static String indent = "  ";

    private boolean isDirty;

    private Document doc;

    private Element root;

    private String file;
}
